//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<python_plug.cpp>
///	@path	~/src/python/
///	@date	2007/12/08
///	@desc	.

#include "config/config.h"

// STL
#include <set>
#include <string>

// python
#include "python/python_plug.h"
#include "python/wrap/expose_python.h"

#include <sysmodule.h>

#include "lib/system/sysinfo.h"
#include "lib/system/thread_debug.h"
#include "lib/utilities/debug.h"
#include "lib/utilities/string.h"
#include "lib/utilities/observer.h"
#include "lib/utilities/dict.h"

using namespace boost::python;

#ifndef _PYXERES
extern int Py_VerboseFlag;
extern int Py_IgnoreEnvironmentFlag;
extern int Py_NoSiteFlag;
#endif

namespace boost { namespace python { namespace converter {

extern const char * defaultEncoding;

} } } // boost.python.converter

namespace xeres
{
	// instance
	PythonPlug& PythonPlug::instance( void )
	{
		static PythonPlug s_pythonPlugInstance;
		return s_pythonPlugInstance;
	}

	namespace
	{
		// declaration stack
		std::deque< PyObject * > s_scopeStack;

		// scope protector for python module registry
		struct _scope_protector
		{
			//@ ctor
			_scope_protector( PyObject * scope )
			{
				// save current scope to stack
				s_scopeStack.push_back( scope );
				// enter current declaration scope
				boost::python::detail::current_scope = scope;
			}
			//@ dtor
			~_scope_protector( void )
			{
				// cleanup declaration scope
				boost::python::detail::current_scope = s_scopeStack.back();
				// pop back scope
				s_scopeStack.pop_back();
			}
		};

		// module name space
		struct _module_ns
		{
			object m_module;
			object m_ns;
		};
	}

	// python main
	struct _PythonMain
	{
		// main module
		object m_mainModule;
		object m_mainNamespace;

		PyThreadState * m_mainThreadState;

		// module
		dict< AString , _module_ns > m_pyModules;

		//@ ctor
		_PythonMain( void )
			: m_mainModule( detail::borrowed_reference( PyImport_AddModule("__main__") ) )
			, m_mainNamespace( detail::borrowed_reference( PyModule_GetDict( m_mainModule.ptr() ) ) )
		{
			_module_ns mns = { m_mainModule , m_mainNamespace };
			m_pyModules[ "__main__" ] = mns;
			m_mainThreadState = PyThreadState_Get();
		}

		//@ dtor
		~_PythonMain( void )
		{
		}

		// find module
		object FindModule( const AString& module_name )
		{
			dict< AString , _module_ns >::iterator it = m_pyModules.find( module_name );
			if( it == m_pyModules.end() )
				return object();
			_module_ns& module = it->second;
			return module.m_module;
		}

		// find namespace
		object FindNamespace( const AString& module_name )
		{
			dict< AString , _module_ns >::iterator it = m_pyModules.find( module_name );
			if( it == m_pyModules.end() )
				return object();
			_module_ns& module = it->second;
			return module.m_ns;
		}
	};

	// ctor
	PythonPlug::PythonPlug( void )
		: m_main( NULL )
		, m_stdout( NULL )
		, m_stderr( NULL )
	{
	}

	// dtor
	PythonPlug::~PythonPlug( void )
	{
		if( IsInitialized() )
			Finalize();
	}

	namespace
	{
		template<
			typename _ExportClass
		> struct _Redirector
		{
		public:

			// ctor
			_Redirector( void )
			{
				m_enable = true;
			}

			void write( const std::string& info )
			{
				if( !m_enable )
					return;

				if( info == "\n" )
				{
					static_cast<_ExportClass*>(this)->output( m_str.c_str() );
					m_str.clear();
				}
				else
				{
					m_str += info;
				}
			}

			void writew( const std::wstring& info )
			{
				if( !m_enable )
					return;

				std::string buf;
				wcs_to_mbs( info , buf );
				if( buf == "\n" )
				{
					static_cast<_ExportClass*>(this)->output( m_str.c_str() );
					m_str.clear();
				}
				else
				{
					m_str += buf;
				}
			}

		private:
			// @ data
			std::string m_str;

		public:
			bool m_enable;
		};

		// Fake
		struct _FakeStdout : public _Redirector<_FakeStdout>
		{
		public:
			void output( const char * str )
			{
				trace_stdout( str );
			}
		};

		struct _FakeStderr : public _Redirector<_FakeStdout>
		{
		public:
			void output( const char * str )
			{
				trace_stderr( str );
			}
		};
	}

#define __declspec(X)
	BOOST_PYTHON_MODULE( helpers )
	{
		boost::python::class_<_FakeStdout , boost::noncopyable>( "fakestdout" )
			.def_readwrite( "enable" , &_FakeStdout::m_enable )
			.def( "write" , &_FakeStdout::write )
			.def( "write" , &_FakeStdout::writew )
		;
		boost::python::class_<_FakeStderr , boost::noncopyable>( "fakestderr" )
			.def_readwrite( "enable" , &_FakeStderr::m_enable )
			.def( "write" , &_FakeStderr::write )
			.def( "write" , &_FakeStderr::writew )
		;
	}
#undef __declspec

#ifndef _PYXERES
	// Initialize
	bool PythonPlug::Initialize( const WString& program , const WString& pythonhome ,
		const WString& pythonPaths , RefWeak<IFileSystem> filesystem )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		if( m_filesystem.IsValid() )
		{
			TRACE_ERROR( _S("PythonPlug::Initialize: Python plug has been initialized.") );
			return false;
		}
		
		AString paths;
		wcs_to_mbs( pythonPaths , paths );

		AString home;
		wcs_to_mbs( pythonhome , home );

		// assign system
		m_filesystem = filesystem;

		Py_SetPythonHome( const_cast<char*>(home.c_str()) );

		// setup python flags
		Py_VerboseFlag = 0;
		Py_IgnoreEnvironmentFlag = 0;
		Py_NoSiteFlag = 1;

		// initialize python
		XS_ASSERT( Py_IsInitialized() == 0 );
		Py_Initialize();

		// set python path
		PySys_SetPath( const_cast<char*>( paths.c_str() ) );

		// FIXME! disable garbage collection if need
		object gc = import( "gc" );
		if( gc != object() )
		{
			gc.attr( "disable" )();
		}
		else
		{
			// GC is not in compiler
			// clear import error
			PyErr_Clear();
		}
		//

		// create main module
		m_main = new _PythonMain;

		// initialize threads
		PyEval_InitThreads();

		return true;
	}

	// Finalize
	void PythonPlug::Finalize( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		// Perform cleanup calls.
		for( size_t i = 0 , total = m_cleanUp.size() ; i < total ; ++i )
		{
			m_cleanUp[i]();
		}
		m_cleanUp.clear();

		assert( m_main );
		delete m_main;
		m_main = NULL;

		m_filesystem = NULL;

		// finalize python
		XS_ASSERT( Py_IsInitialized() != 0 );
		Py_Finalize();
	}

	// IsInitialized
	bool PythonPlug::IsInitialized( void )
	{
		return Py_IsInitialized() != 0;
	}
#else
	// Initialize
	bool PythonPlug::Initialize( RefWeak<IFileSystem> filesystem )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		if( m_filesystem.IsValid() )
		{
			TRACE_ERROR( _S("PythonPlug::Initialize: Python plug has been initialized.") );
			return false;
		}

		// assign system
		m_filesystem = filesystem;

		// create main module
		m_main = new _PythonMain;

		// initialize threads
		return true;
	}

	// Finalize
	void PythonPlug::Finalize( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		// Perform cleanup calls.
		for( size_t i = 0 , total = m_cleanUp.size() ; i < total ; ++i )
		{
			m_cleanUp[i]();
		}
		m_cleanUp.clear();

		assert( m_main );
		delete m_main;
		m_main = NULL;

		if( m_stdout || m_stderr )
		{
			RestoreStdio();
		}

		m_filesystem = NULL;
	}

	// IsInitialized
	bool PythonPlug::IsInitialized( void )
	{
		return m_main != NULL;
	}
#endif
	// RedirStdio
	void PythonPlug::RedirStdio( void )
	{
		if( m_stdout || m_stderr )
		{
			TRACE_ERROR( _S("PythonPlug::RedirStdio: Stdio has been redirected.") );
			return;
		}
		m_stdout = PySys_GetObject( "stdout" );
		m_stderr = PySys_GetObject( "stderr" );
		Py_INCREF( m_stdout );
		Py_INCREF( m_stdout );

		// Register fake output handles.
 		RegisterModule( "xeres_stdio_helpers" , init_module_helpers );
 		object helpers = import( "xeres_stdio_helpers" );
		object fakestdout = helpers.attr("fakestdout")();
 		object fakestderr = helpers.attr("fakestderr")();

		PySys_SetObject( "stdout" , fakestdout.ptr() );
		PySys_SetObject( "stderr" , fakestderr.ptr() );
	}
	// RestoreStdio
	void PythonPlug::RestoreStdio( void )
	{
		if( m_stdout == NULL || m_stderr == NULL )
		{
			TRACE_ERROR( _S("PythonPlug::RestoreStdio: Stdio is not redirected.") );
			return;
		}
		PySys_SetObject( "stdout" , m_stdout );
		PySys_SetObject( "stderr" , m_stderr );
		Py_DECREF(m_stdout);
		Py_DECREF(m_stderr);
		m_stdout = NULL;
		m_stderr = NULL;
	}
	// EnableStdout
	void PythonPlug::EnableStdout( bool enable_stdout , bool enable_stderr )
	{
		object out = object( handle<>( PySys_GetObject( "stdout" ) ) );
		object err = object( handle<>( PySys_GetObject( "stderr" ) ) );
		try
		{
			out.attr("enable") = enable_stdout;
		}
		catch( error_already_set )
		{
			PyErr_Print();
			PyErr_Clear();
		}
		try
		{
			err.attr("enable") = enable_stderr;
		}
		catch( error_already_set )
		{
			PyErr_Print();
			PyErr_Clear();
		}
	}
	// SetEncoding
	void PythonPlug::SetEncoding( const char * encoding )
	{
		if( ( std::string("utf-8") == encoding ) ||
			( std::string("latin-1") == encoding ) ||
			( std::string("mbcs") == encoding ) ||
			( std::string("ascii") == encoding ) )
		{
			boost::python::converter::defaultEncoding = encoding;
		}
		else
		{
			TRACE_ERROR( _S("PythonPlug::SetEncoding: Illegal encoding '%s'.") , encoding );
		}
	}

	// RegisterModule
	bool PythonPlug::RegisterModule( const char * module_name , function<void(void)> module_call )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		object the_module = m_main->FindModule( module_name );
		if( the_module == object() )
		{// add new module
			the_module = object( borrowed( PyImport_AddModule( (char*)module_name ) ) );
			PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } };
			Py_InitModule( const_cast<char*>(module_name) , initial_methods );
			object the_ns( detail::borrowed_reference( PyModule_GetDict( the_module.ptr() ) ) );
			_module_ns mns = { the_module , the_ns };
			m_main->m_pyModules[ module_name ] = mns;
		}
		// setup protector
		//scope current_module(the_module);
		_scope_protector protector( the_module.ptr() );

		bool result = false;
		// do register module
		try
		{
			module_call();
			result = true;
		}
		catch( error_already_set )
		{
			// generate exception
			PyErr_Print();
			PyErr_Clear();
		}
		return result;
	}

	// RegisterObject
	void PythonPlug::RegisterObject( const char * module_name , const char * name , object obj )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		object the_ns = m_main->FindNamespace( module_name );
		if( the_ns == object() )
		{// add new module
			object the_module = object( detail::borrowed_reference( PyImport_AddModule( (char*)module_name ) ) );
			PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } };
			Py_InitModule( (char*)module_name , initial_methods );
			object the_ns( detail::borrowed_reference( PyModule_GetDict( the_module.ptr() ) ) );
			_module_ns mns = { the_module , the_ns };
			m_main->m_pyModules[ module_name ] = mns;
		}
		the_ns[ name ] = obj;
	}
	// RegisterObject
	void PythonPlug::RegisterObject( const char * module_name , const char * name , PyObject * obj )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		object the_ns = m_main->FindNamespace( module_name );
		if( the_ns == object() )
		{// add new module
			object the_module = object( detail::borrowed_reference( PyImport_AddModule( (char*)module_name ) ) );
			PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } };
			Py_InitModule( (char*)module_name , initial_methods );
			object the_ns( detail::borrowed_reference( PyModule_GetDict( the_module.ptr() ) ) );
			_module_ns mns = { the_module , the_ns };
			m_main->m_pyModules[ module_name ] = mns;
		}
		the_ns[ name ] = handle<>( obj );
	}
	// FindMember
	object PythonPlug::FindMember( const char * module_name , const char * member_name )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		object the_ns = m_main->FindNamespace( module_name );
		if( the_ns == NULL )
			return object();
		return the_ns[ member_name ];
	}

	// RunString
	bool PythonPlug::RunString( const char * string )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		bool res = true;
		try
		{
			if( PyErr_Occurred() != NULL )
			{
				PyErr_Print();
				PyErr_Clear();
			}

			// run
			PyCompilerFlags flags;
			flags.cf_flags = 0;
			// run
			handle<> result((allow_null( PyRun_String(
				string , Py_single_input ,
				m_main->m_mainNamespace.ptr() , m_main->m_mainNamespace.ptr() ) ) ));

			if( PyErr_Occurred() != NULL )
			{
				PyErr_Print();
				PyErr_Clear();
				res = false;
			}

			if( Py_FlushLine() )
				PyErr_Clear();

			if( result )
			{
				if( result.get() != Py_None )
					PyObject_Print( result.get() , stdout , 0 );
			}
		}
		catch( error_already_set )
		{
			PyErr_Print();
			PyErr_Clear();
			res = false;
		}
		return res;
	}

	// RunFile
	void PythonPlug::RunFile( const char * filename )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		FILE * file = m_filesystem->OpenPosixFile( filename , "r" );
		if( file == NULL )
		{
			TRACE_ERROR( _S("PythonPlug::RunFile: Cannot find <%S>.") , filename );
			return;
		}

		try
		{
			PyCompilerFlags flags;
			flags.cf_flags = 0;
			// run
			PyObject * result = PyRun_FileExFlags(
				file , filename , Py_file_input ,
				m_main->m_mainNamespace.ptr() , NULL ,
				0 , &flags );

			if( PyErr_Occurred() != NULL )
			{
				PyErr_Print();
				PyErr_Clear();
			}

			if( result )
				Py_DECREF(result);
		}
		catch( error_already_set )
		{
			PyErr_Print();
			PyErr_Clear();
		}
		fclose( file );
	}

	// RunExpression
	object PythonPlug::RunExpression( const char * expr )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main|tick") );

		object result = object();

		try
		{
			PyCompilerFlags flags;
			flags.cf_flags = 0;
			// run
			PyObject * obj = PyRun_StringFlags(
				expr , Py_eval_input ,
				m_main->m_mainNamespace.ptr() , NULL , &flags );
			
			if( PyErr_Occurred() != NULL )
			{
				PyErr_Print();
				PyErr_Clear();
			}
			else
			{
				result = object( detail::new_reference( obj ) );
			}
		}
		catch( error_already_set )
		{
			PyErr_Print();
			PyErr_Clear();
		}
		return result;
	}

	// InlineCode
	object PythonPlug::InlineCode( const char * module_name , const char * name , const char * script )
	{
		object code( detail::new_reference( Py_CompileStringFlags( script , name , Py_file_input , NULL ) ) );
		if( PyErr_Occurred() != NULL )
		{
			PyErr_Print();
			PyErr_Clear();
			return object();
		}
		object module( detail::new_reference( PyImport_ExecCodeModule( (char*)module_name , code.ptr() ) ) );
		if( PyErr_Occurred() != NULL )
		{
			PyErr_Print();
			PyErr_Clear();
			return object();
		}
		return module;
	}

	// InlineCompiled
	boost::python::object PythonPlug::InlineCompiled( const char * module_name , boost::python::object co )
	{
		object module( detail::new_reference( PyImport_ExecCodeModule( (char*)module_name , co.ptr() ) ) );
		if( PyErr_Occurred() != NULL )
		{
			PyErr_Print();
			PyErr_Clear();
			return object();
		}
		return module;
	}

	// RegisterCleanup
	void PythonPlug::RegisterCleanup( function<void()> call )
	{
		m_cleanUp.push_back( call );
	}

} // namespace xeres
